<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ bot_name }}</title>
    <!-- Live2D -->
    <link href="https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/src/live2d.min.css" rel="stylesheet" />
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap');

        :root {
            /* Default Theme */
            --primary-color: #F078A9;
            --secondary-color: #FFD9E8;
            --background-color: #FFF0F6;
            --text-color: #4F0D23;
            --chat-user-bg: #FFD9E8;
            --chat-bot-bg: #FFF5FA;
            --button-hover: #F495B6;
            --shadow-color: rgba(240, 120, 169, 0.2);
        }

        /* Dark Theme */
        [data-theme="dark"] {
            --primary-color: #FF96B7;
            --secondary-color: #4A314D;
            --background-color: #2D1B2E;
            --text-color: #FFE6F0;
            --chat-user-bg: #4A314D;
            --chat-bot-bg: #3D2840;
            --button-hover: #FF7AA3;
            --shadow-color: rgba(255, 150, 183, 0.2);
        }

        /* Pastel Theme */
        [data-theme="pastel"] {
            --primary-color: #B5D8F7;
            --secondary-color: #E8F4FF;
            --background-color: #F0F9FF;
            --text-color: #2B5174;
            --chat-user-bg: #E8F4FF;
            --chat-bot-bg: #F5FAFF;
            --button-hover: #9ECEF5;
            --shadow-color: rgba(181, 216, 247, 0.2);
        }

        /* Mint Theme */
        [data-theme="mint"] {
            --primary-color: #7DCEA0;
            --secondary-color: #E0F5E9;
            --background-color: #F0FFF4;
            --text-color: #2C4A3E;
            --chat-user-bg: #E0F5E9;
            --chat-bot-bg: #F0FAF5;
            --button-hover: #6BB98D;
            --shadow-color: rgba(125, 206, 160, 0.2);
        }

        /* Lavender Theme */
        [data-theme="lavender"] {
            --primary-color: #9B8AD8;
            --secondary-color: #E8E4F5;
            --background-color: #F5F2FF;
            --text-color: #3A2F5B;
            --chat-user-bg: #E8E4F5;
            --chat-bot-bg: #F2EFFA;
            --button-hover: #8878C7;
            --shadow-color: rgba(155, 138, 216, 0.2);
        }

        /* Sunset Theme */
        [data-theme="sunset"] {
            --primary-color: #FF9B7D;
            --secondary-color: #FFE8E0;
            --background-color: #FFF4F0;
            --text-color: #5B3629;
            --chat-user-bg: #FFE8E0;
            --chat-bot-bg: #FFF0EA;
            --button-hover: #FF8A69;
            --shadow-color: rgba(255, 155, 125, 0.2);
        }

        /* Trans Pride Theme */
        [data-theme="trans"] {
            --primary-color: #5BCEFA;
            --secondary-color: #F5A9B8;
            --background-color: #FFF5F8;
            --text-color: #2D2D2D;
            --chat-user-bg: #E0F4FF;
            --chat-bot-bg: #FFE8EE;
            --button-hover: #4AB3E4;
            --shadow-color: rgba(91, 206, 250, 0.15);
        }


        body {
            margin: 0;
            padding: 0;
            background: linear-gradient(135deg, var(--background-color) 0%, #FFFFFF 100%);
            font-family: 'Quicksand', sans-serif;
            color: var(--text-color);
            min-height: 100vh;
            transition: all 0.3s ease;
        }

        header {
            text-align: center;
            padding: 1.5rem;
            background-color: var(--secondary-color);
            border-bottom: 2px solid var(--primary-color);
            box-shadow: 0 2px 10px var(--shadow-color);
            position: relative;
        }

        header h1 {
            margin: 0;
            font-size: 2rem;
            font-weight: 700;
            color: var(--text-color);
            letter-spacing: 1px;
        }

        .theme-switcher {
            position: absolute;
            right: 20px;
            top: 50%;
            transform: translateY(-50%);
            display: flex;
            gap: 10px;
        }

        .theme-switcher select {
            padding: 8px 12px;
            border-radius: 20px;
            border: 2px solid var(--primary-color);
            background: var(--secondary-color);
            color: var(--text-color);
            font-family: 'Quicksand', sans-serif;
            cursor: pointer;
            outline: none;
        }

        .chat-container {
            width: 90%;
            max-width: 800px;
            margin: 2rem auto;
            background-color: rgba(255, 255, 255, 0.9);
            border-radius: 24px;
            box-shadow: 0 8px 32px var(--shadow-color);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            backdrop-filter: blur(10px);
            transition: all 0.3s ease;
        }

        #chat-log {
            flex: 1;
            padding: 2rem;
            overflow-y: auto;
            max-height: 60vh;
            box-sizing: border-box;
            scroll-behavior: smooth;
        }

        .message {
            margin-bottom: 1.5rem;
            line-height: 1.6;
            display: inline-block;
            max-width: 80%;
            word-wrap: break-word;
            padding: 1rem 1.5rem;
            border-radius: 20px;
            font-size: 1rem;
            animation: fadeIn 0.3s ease;
            box-shadow: 0 2px 8px var(--shadow-color);
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(10px);
            }

            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        .message.user {
            background-color: var(--chat-user-bg);
            color: var(--text-color);
            text-align: right;
            float: right;
            clear: both;
            border-bottom-right-radius: 4px;
        }

        .message.bot {
            background-color: var(--chat-bot-bg);
            color: var(--text-color);
            text-align: left;
            float: left;
            clear: both;
            border-bottom-left-radius: 4px;
        }

        .input-container {
            display: flex;
            padding: 1rem;
            border-top: 2px solid var(--secondary-color);
            background-color: rgba(255, 255, 255, 0.9);
            backdrop-filter: blur(10px);
            gap: 10px;
        }

        #user-input {
            flex: 1;
            padding: 1rem 1.5rem;
            border: 2px solid var(--secondary-color);
            border-radius: 25px;
            outline: none;
            font-size: 1rem;
            background-color: rgba(255, 255, 255, 0.9);
            color: var(--text-color);
            transition: all 0.3s ease;
        }

        #user-input:focus {
            border-color: var(--primary-color);
            box-shadow: 0 0 10px var(--shadow-color);
        }

        #user-input::placeholder {
            color: var(--text-color);
            opacity: 0.6;
        }

        button {
            padding: 0.8rem 1.5rem;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-size: 1rem;
            font-weight: 600;
            font-family: 'Quicksand', sans-serif;
            transition: all 0.3s ease;
            box-shadow: 0 2px 8px var(--shadow-color);
        }

        #send-btn {
            background-color: var(--primary-color);
            color: white;
        }

        #send-btn:hover {
            background-color: var(--button-hover);
            transform: translateY(-2px);
        }

        #reset-btn {
            background-color: var(--secondary-color);
            color: var(--text-color);
        }

        #reset-btn:hover {
            background-color: var(--chat-user-bg);
            transform: translateY(-2px);
        }

        @media (max-width: 768px) {
            .chat-container {
                width: 95%;
                margin: 1rem auto;
            }

            .message {
                max-width: 90%;
            }

            .input-container {
                flex-wrap: wrap;
            }

            button {
                flex: 1;
                min-width: 120px;
            }
        }
    </style>
</head>

<body>
    <header>
        <h1>{{ bot_name }}</h1>
        <div class="theme-switcher">
            <select id="theme-select">
                <option value="default">Sweet Pink</option>
                <option value="dark">Dark Enchanting</option>
                <option value="pastel">Fresh & Elegant</option>
                <option value="mint">Mint Garden</option>
                <option value="lavender">Lavender Dream</option>
                <option value="sunset">Warm Sunset</option>
                <option value="trans">Trans Pride</option>
            </select>
            <select id="model-select">
                <option value="koharu">Koharu</option>
                <option value="haruto">Haruto</option>
                <option value="chitose">Chitose</option>
                <option value="miku">Miku</option>
                <option value="shizuku">Shizuku</option>
            </select>
        </div>
    </header>

    <div class="chat-container">
        <div id="chat-log"></div>
        <div class="input-container">
            <input type="text" id="user-input" placeholder="Chat with me ❤️" />
            <button id="send-btn">Send</button>
            <button id="reset-btn">Reset</button>
        </div>
    </div>

    <!-- Live2D Widget -->
    <script src="https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js"></script>
    <script>
        // ==================== Live2D 配置 ====================
        const modelMap = {
            'koharu': "https://unpkg.com/live2d-widget-model-koharu@1.0.5/assets/koharu.model.json",
            'haruto': "https://unpkg.com/live2d-widget-model-haruto@1.0.5/assets/haruto.model.json",
            'chitose': "https://unpkg.com/live2d-widget-model-chitose@1.0.5/assets/chitose.model.json",
            'miku': "https://unpkg.com/live2d-widget-model-miku@1.0.5/assets/miku.model.json",
            'shizuku': "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json"
        };

        const modelSelect = document.getElementById('model-select');
        const savedModel = localStorage.getItem('live2d_model') || 'koharu';
        modelSelect.value = savedModel;

        function initLive2D(modelName) {
            if (window.L2Dwidget) {
                L2Dwidget.init({
                    "model": {
                        jsonPath: modelMap[modelName],
                        scale: 1
                    },
                    "display": {
                        "position": "right",
                        "width": 150,
                        "height": 300,
                        "hOffset": 0,
                        "vOffset": -20
                    },
                    "mobile": {
                        "show": true,
                        "scale": 0.5
                    },
                    "react": {
                        "opacityDefault": 0.7,
                        "opacityOnHover": 0.2
                    }
                });
            }
        }

        initLive2D(savedModel);

        modelSelect.addEventListener('change', (e) => {
            const modelName = e.target.value;
            localStorage.setItem('live2d_model', modelName);
            window.location.reload();
        });

        // ==================== 主题设置 ====================
        const themeSelect = document.getElementById('theme-select');
        const savedTheme = localStorage.getItem('theme') || 'pastel';
        document.documentElement.setAttribute('data-theme', savedTheme);
        themeSelect.value = savedTheme;

        themeSelect.addEventListener('change', (e) => {
            const theme = e.target.value;
            document.documentElement.setAttribute('data-theme', theme);
            localStorage.setItem('theme', theme);
        });

        // ==================== 聊天功能 ====================
        const botName = '{{ bot_name }}';
        let conversationId = localStorage.getItem('conversation_id') || '';

        const chatLog = document.getElementById('chat-log');
        const userInput = document.getElementById('user-input');
        const sendBtn = document.getElementById('send-btn');
        const resetBtn = document.getElementById('reset-btn');

        sendBtn.addEventListener('click', sendMessage);
        userInput.addEventListener('keypress', function (event) {
            if (event.key === 'Enter') {
                sendMessage();
            }
        });
        resetBtn.addEventListener('click', resetConversation);

        // ==================== TTS (并发 <=5) + 串行播放 + 保证顺序 ====================
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        let currentAudioSource = null;
        let isPlaying = false;

        // ---- 并发控制 ----
        const MAX_CONCURRENT_TTS = 5;
        let currentTtsRequests = 0;       // 正在发 TTS 请求的数量
        let ttsRequestQueue = [];         // 等待发起的 TTS 请求

        // ---- 播放队列（串行） + 段编号保证顺序 ----
        let ttsPlaybackQueue = [];        // 只是要播放的队列
        let pendingSegments = {};         // segmentId -> { text, buffer, ready }
        let globalSegmentId = 0;          // 自增分段ID
        let lastPlayedId = -1;            // 上一个已播放的ID

        // ---- SSE增量缓冲，避免一字一播 ----
        let accumulatedSseBuffer = '';
        const MIN_LENGTH = 20;            // 至少20字才强制一次
        const SPLIT_PATTERN = /[。.!？?]/; // 句子终止符

        /**
         * 串行播放队列：一次只播一个
         */
        function processPlaybackQueue() {
            if (isPlaying || ttsPlaybackQueue.length === 0) return;

            const item = ttsPlaybackQueue.shift();
            if (!item.buffer) return; // 理论上不会出现

            isPlaying = true;

            currentAudioSource = audioContext.createBufferSource();
            currentAudioSource.buffer = item.buffer;
            currentAudioSource.connect(audioContext.destination);

            currentAudioSource.onended = () => {
                isPlaying = false;
                processPlaybackQueue();
            };
            currentAudioSource.start();
        }

        /**
         * 当某个段的 TTS buffer 就绪后，检查能否按顺序推进
         */
        function attemptQueuePlay() {
            // 不断查看 `lastPlayedId + 1` 这个段是否 ready
            while (true) {
                const nextId = lastPlayedId + 1;
                if (!pendingSegments[nextId]) {
                    break;  // 还没这个段
                }
                if (!pendingSegments[nextId].ready) {
                    break;  // 还没解码完
                }
                // 可以播这个段了
                const seg = pendingSegments[nextId];
                lastPlayedId++;
                // 推入播放队列
                ttsPlaybackQueue.push({ buffer: seg.buffer });
                // 移除记录
                delete pendingSegments[nextId];
            }
            // 检查完毕后，尝试播放队列
            processPlaybackQueue();
        }

        /**
         * 发起并发受限的 TTS 请求
         */
        function limitedFetchTtsBuffer(text) {
            return new Promise((resolve, reject) => {
                ttsRequestQueue.push({ text, resolve, reject });
                tryStartNextTtsRequest();
            });
        }

        function tryStartNextTtsRequest() {
            if (ttsRequestQueue.length === 0) return;
            if (currentTtsRequests >= MAX_CONCURRENT_TTS) return;

            const { text, resolve, reject } = ttsRequestQueue.shift();
            currentTtsRequests++;

            fetchTtsBuffer(text)
                .then((buf) => {
                    resolve(buf);
                    currentTtsRequests--;
                    tryStartNextTtsRequest(); // 再拉下一个
                })
                .catch((err) => {
                    reject(err);
                    currentTtsRequests--;
                    tryStartNextTtsRequest();
                });
        }

        /**
         * 真正请求 /pink/tts 接口，返回解码后的 AudioBuffer
         */
        async function fetchTtsBuffer(text) {
            const ttsResponse = await fetch('./pink/tts', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    content_text: text,
                    conversation_id: conversationId
                })
            });
            if (!ttsResponse.ok) {
                throw new Error('TTS response was not ok');
            }
            const reader = ttsResponse.body.getReader();
            const chunks = [];
            while (true) {
                const { value, done } = await reader.read();
                if (done) break;
                chunks.push(value);
            }
            const audioData = new Uint8Array(chunks.reduce((acc, c) => acc + c.length, 0));
            let offset = 0;
            for (const c of chunks) {
                audioData.set(c, offset);
                offset += c.length;
            }
            const audioBuffer = await audioContext.decodeAudioData(audioData.buffer);
            return audioBuffer;
        }

        /**
         * 新生成一个段，分配全局ID，并发请求 TTS
         */
        function pushSegmentForTts(textSegment) {
            const segId = globalSegmentId++;
            // 初始化 pendingSegments
            pendingSegments[segId] = {
                text: textSegment,
                buffer: null,
                ready: false
            };
            // 发起 TTS
            limitedFetchTtsBuffer(textSegment)
                .then((buf) => {
                    pendingSegments[segId].buffer = buf;
                    pendingSegments[segId].ready = true;
                    // 尝试播放下一个可播段
                    attemptQueuePlay();
                })
                .catch((err) => {
                    console.error('TTS fetch error:', err);
                    // 出错的段可以直接删除或保留
                    delete pendingSegments[segId];
                });
        }

        /**
         * SSE 增量累加到缓冲; 当满足终止符或长度 >= 20，则切出段
         */
        function accumulateAndTts(newText) {
            accumulatedSseBuffer += newText;

            while (true) {
                const match = accumulatedSseBuffer.match(SPLIT_PATTERN);
                if (!match) {
                    // 没有句子终止符
                    if (accumulatedSseBuffer.length >= MIN_LENGTH) {
                        // 已经超长 => 直接把这段全部拿去 TTS
                        pushSegmentForTts(accumulatedSseBuffer.trim());
                        accumulatedSseBuffer = '';
                    }
                    break;
                } else {
                    // 找到一个终止符
                    const boundaryIndex = match.index;
                    // segment包含这个标点
                    let segment = accumulatedSseBuffer.slice(0, boundaryIndex + 1).trim();
                    if (segment) {
                        pushSegmentForTts(segment);
                    }
                    accumulatedSseBuffer = accumulatedSseBuffer.slice(boundaryIndex + 1);
                    // while 循环继续看后续是否还有下一个终止符
                }
            }
        }

        // ==================== SSE + UI 显示 + 发送消息 ====================
        async function sendMessage() {
            const message = userInput.value.trim();
            if (!message) return;

            appendMessage(message, 'user');
            userInput.value = '';

            const requestBody = {
                query: message,
                conversation_id: conversationId
            };

            try {
                const response = await fetch('./pink/talk', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(requestBody)
                });

                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }

                let botMessageContainer = appendMessage('', 'bot');
                const reader = response.body.getReader();
                const decoder = new TextDecoder('utf-8');
                let buffer = '';
                let accumulatedText = '';

                while (true) {
                    const { value, done } = await reader.read();
                    if (done) break;

                    buffer += decoder.decode(value, { stream: true });
                    const lines = buffer.split('\n\n');
                    buffer = lines.pop() || '';

                    for (const line of lines) {
                        if (!line.trim()) continue;
                        try {
                            const data = JSON.parse(line);
                            if (data.answer) {
                                // 1) UI 显示
                                accumulatedText += data.answer;
                                botMessageContainer.textContent = botName + ': ' + accumulatedText;
                                // add to #waifu-tips and add waifu-tips-active class to it
                                const waifuTips = document.getElementById('waifu-tips');
                                waifuTips.textContent += data.answer;
                                waifuTips.classList.add('waifu-tips-active');
                                // 2) 累加 -> 检查是否满足分段条件
                                accumulateAndTts(data.answer);
                            }
                            if (data.conversation_id) {
                                conversationId = data.conversation_id;
                                localStorage.setItem('conversation_id', conversationId);
                            }
                        } catch (error) {
                            console.error('Parsing SSE chunk error:', error, line);
                        }
                    }
                }

                // SSE 结束后，如果缓冲还有余料，最后flush一次
                if (accumulatedSseBuffer.trim().length > 0) {
                    pushSegmentForTts(accumulatedSseBuffer.trim());
                    accumulatedSseBuffer = '';
                }

            } catch (error) {
                console.error('Error:', error);
                appendMessage('Sorry, something went wrong. Please try again later!', 'bot');
            }
        }

        // ==================== 工具函数 ====================
        function appendMessage(text, sender) {
            const messageEl = document.createElement('div');
            messageEl.className = `message ${sender}`;
            if (sender === 'bot') {
                messageEl.textContent = botName + ': ' + text;
            } else {
                messageEl.textContent = text;
            }
            chatLog.appendChild(messageEl);
            chatLog.scrollTop = chatLog.scrollHeight;
            return messageEl;
        }

        function resetConversation() {
            localStorage.removeItem('conversation_id');
            conversationId = '';
            chatLog.innerHTML = '';
            appendMessage('Let\'s start over!', 'bot');

            // 清理各类队列 & 状态
            ttsRequestQueue = [];
            ttsPlaybackQueue = [];
            pendingSegments = {};
            globalSegmentId = 0;
            lastPlayedId = -1;
            accumulatedSseBuffer = '';

            currentTtsRequests = 0;
            isPlaying = false;

            if (currentAudioSource) {
                currentAudioSource.stop();
            }
        }

        // 首次加载时的欢迎语
        window.onload = () => {
            const welcomeMessage = 'Hi! I\'m ' + botName + ', nice to meet you!';
            appendMessage(welcomeMessage, 'bot');
        };
    </script>
</body>

</html>